package com.derbysoft.synxisadapter.dswitch3.service.impl;

import com.derbysoft.dswitch.dto.hotel.common.CancelPolicyDTO;
import com.derbysoft.dswitch.remote.hotel.dto.ErrorDTO;
import com.derbysoft.dswitch.remote.hotel.dto.HotelReservationRequest;
import com.derbysoft.dswitch.remote.hotel.dto.HotelReservationResponse;
import com.derbysoft.synxisadapter.commons.dto.synxis.ArrayOfErrorType;
import com.derbysoft.synxisadapter.commons.dto.synxis.OTAHotelResRQ;
import com.derbysoft.synxisadapter.commons.dto.synxis.OTAHotelResRS;
import com.derbysoft.synxisadapter.commons.exception.MakeReservationException;
import com.derbysoft.synxisadapter.commons.model.Channel;
import com.derbysoft.synxisadapter.commons.model.Reservation;
import com.derbysoft.synxisadapter.commons.repository.ReservationRepository;
import com.derbysoft.synxisadapter.commons.utils.ErrorMessageUtils;
import com.derbysoft.synxisadapter.commons.utils.MailSender;
import com.derbysoft.synxisadapter.dswitch2.translator.Translator;
import com.derbysoft.synxisadapter.dswitch3.service.HotelProviderService;
import com.derbysoft.synxisadapter.dswitch3.translator.AbstractTranslator;
import com.derbysoft.synxisadapter.webservice.SynXisWebService;
import com.derbysoft.synxisadapter.webservice.impl.ResponsePair;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * User: jason
 * Date: 2012-11-21
 */
@Component("ds3ReservationService")
public class ReservationServiceImpl extends DS3AbstractService
        implements HotelProviderService<HotelReservationRequest, HotelReservationResponse> {

    private static Logger logger = LoggerFactory.getLogger(ReservationServiceImpl.class);

    @Autowired
    @Qualifier("ds3PreviewService")
    private HotelProviderService<HotelReservationRequest, HotelReservationResponse> ds3PreviewService;

    @Autowired
    @Qualifier("ds3HotelResRQTranslator")
    private Translator<HotelReservationRequest, OTAHotelResRQ, Void> ds3HotelResRQTranslator;

    @Autowired
    @Qualifier("ds3HotelResRSTranslator")
    private Translator<List<OTAHotelResRS>, HotelReservationResponse, HotelReservationRequest> ds3HotelResRSTranslator;

    @Autowired
    @Qualifier("reservationWebService")
    private SynXisWebService<OTAHotelResRQ, OTAHotelResRS> reservationWebService;

    @Autowired
    @Qualifier("taskExecutor")
    private Executor executor;

    @Autowired
    private JavaMailSender javaMailSender;

    @Autowired
    private ReservationRepository reservationRepository;

    @Override
    public HotelReservationResponse execute(HotelReservationRequest request) {
        try {
            Channel channel = loadChannel(request.getHeader().getSource());
            HotelReservationResponse previewResponse = ds3PreviewService.execute(request);
            if (previewResponse.getError() != null) {
                updateStatus(request.getHotelReservationRQ().getErsp(), Reservation.Status.BOOK_FAILED);
                return previewResponse;
            }

            OTAHotelResRQ otaHotelResRQ = ds3HotelResRQTranslator.translate(request, null);
            if (request.getHotelReservationRQ().getHotelReservation().getRoomStaysList().get(0).getNumberOfUnits() > 1) {
                return makeMultiReservation(request, otaHotelResRQ, previewResponse.getHotelReservationRS().getCancelPolicy(), channel);
            }
            return makeReservation(request, otaHotelResRQ, previewResponse.getHotelReservationRS().getCancelPolicy(), channel);
        } catch (Exception ex) {
            updateStatus(request.getHotelReservationRQ().getErsp(), Reservation.Status.BOOK_FAILED);
            logger.error(ErrorMessageUtils.buildErrorMessage(ex));
            return createErrorResponse(request, ex);
        }
    }

    private HotelReservationResponse makeReservation(HotelReservationRequest request,
                                                     OTAHotelResRQ otaHotelResRQ, CancelPolicyDTO cancelPolicy, Channel channel) throws Exception {
        ArrayList<OTAHotelResRS> otaHotelResRSes = new ArrayList<OTAHotelResRS>();
        otaHotelResRSes.add(reservationWebService.invoke(otaHotelResRQ, channel));
        if (isFailedReservation(otaHotelResRSes)) {
            return failedReservation(otaHotelResRSes.get(0).getErrors(), request);
        }
        saveReservationItems(request, otaHotelResRSes);
        updateStatus(request.getHotelReservationRQ().getErsp(), Reservation.Status.CONFIRMED);
        HotelReservationResponse response = ds3HotelResRSTranslator.translate(otaHotelResRSes, request);
        response.getHotelReservationRS().setCancelPolicy(cancelPolicy);
        return response;
    }

    private HotelReservationResponse failedReservation(ArrayOfErrorType arrayOfErrorType, HotelReservationRequest request) {
        HotelReservationResponse response = new HotelReservationResponse();
        response.setTaskId(request.getHeader().getTaskId());
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setCode("BookFailed");
        errorDTO.setSource(AbstractTranslator.PROVIDER_SOURCE);
        errorDTO.setMessage(CollectionUtils.isNotEmpty(arrayOfErrorType.getError()) ? arrayOfErrorType.getError().get(0).getShortText() : " ");
        response.setError(errorDTO);
        return response;
    }

    private boolean isFailedReservation(List<OTAHotelResRS> otaHotelResRSes) {
        return otaHotelResRSes.get(0).getErrors() != null;
    }

    private HotelReservationResponse makeMultiReservation(HotelReservationRequest request,
                                                          OTAHotelResRQ otaHotelResRQ, CancelPolicyDTO cancelPolicy, Channel channel) throws Exception {
        List<OTAHotelResRS> otaHotelResRSes = multiReservation(otaHotelResRQ, request, channel);
        saveReservationItems(request, otaHotelResRSes);
        updateStatus(request.getHotelReservationRQ().getErsp(), Reservation.Status.CONFIRMED);
        HotelReservationResponse response = ds3HotelResRSTranslator.translate(otaHotelResRSes, request);
        response.getHotelReservationRS().setCancelPolicy(cancelPolicy);
        return response;
    }

    private void saveReservationItems(HotelReservationRequest request, List<OTAHotelResRS> otaHotelResRSes) {
        String ratePlaneCode = request.getHotelReservationRQ().getHotelReservation().getRoomStaysList().get(0).getRatePlanCode();
        String roomTypeCode = request.getHotelReservationRQ().getHotelReservation().getRoomStaysList().get(0).getRoomTypeCode();
        String ersp = request.getHotelReservationRQ().getErsp();
        for (OTAHotelResRS otaHotelResRS : otaHotelResRSes) {
            String confirmNumber = otaHotelResRS.getHotelReservations().getHotelReservation().get(0).getUniqueID().getID();
            reservationRepository.addReservationItem(ersp, roomTypeCode, ratePlaneCode, confirmNumber);
        }
    }

    private List<OTAHotelResRS> multiReservation(OTAHotelResRQ otaHotelResRQ, HotelReservationRequest request, Channel channel) throws Exception {
        List<OTAHotelResRQ> otaHotelResRQs = new ArrayList<OTAHotelResRQ>();
        for (int i = 0; i < request.getHotelReservationRQ().getHotelReservation().getRoomStaysList().get(0).getNumberOfUnits(); i++) {
            otaHotelResRQs.add(otaHotelResRQ);
        }
        List<OTAHotelResRS> otaHotelResRSes = new ArrayList<OTAHotelResRS>();
        List<ResponsePair<OTAHotelResRQ, OTAHotelResRS>> responsePairs = reservationWebService.batchInvoke(otaHotelResRQs, channel);
        for (ResponsePair<OTAHotelResRQ, OTAHotelResRS> responsePair : responsePairs) {
            OTAHotelResRS otaHotelResRS = responsePair.getResponse();
            if (otaHotelResRS.getErrors() != null) {
                executor.execute(new MailSender(javaMailSender, "Make reservation failed !!", otaHotelResRS.getErrors().getError().get(0).getShortText()));
                throw new MakeReservationException(otaHotelResRS.getErrors().getError().get(0).getShortText());
            } else {
                otaHotelResRSes.add(otaHotelResRS);
            }
        }
        return otaHotelResRSes;
    }

    private HotelReservationResponse createErrorResponse(HotelReservationRequest request, Exception ex) {
        HotelReservationResponse response = new HotelReservationResponse();
        response.setTaskId(request.getHeader().getTaskId());
        response.setError(createDS3ErrorDTO(ex));
        return response;
    }

    private void updateStatus(String ersp, Reservation.Status status) {
        if (ersp == null) {
            return;
        }
        reservationRepository.updateStatus(ersp, status);
    }
}
